home *** CD-ROM | disk | FTP | other *** search
/ Almathera Ten Pack 3: CDPD 3 / Almathera Ten on Ten - Disc 3: CDPD3.iso / fish / 001-100 / 001-025 / 002 / portar / portar.c < prev    next >
C/C++ Source or Header  |  1995-03-17  |  33KB  |  1,408 lines

  1. /*
  2.  *
  3.  *
  4.  * The    information  in  this  document  is  subject  to  change
  5.  * without  notice  and  should not be construed as a commitment
  6.  * by Digital Equipment Corporation or by DECUS.
  7.  *
  8.  * Neither Digital Equipment Corporation, DECUS, nor the authors
  9.  * assume any responsibility for the use or reliability of  this
  10.  * document or the described software.
  11.  *
  12.  *    Copyright (C) 1980, DECUS
  13.  *
  14.  *
  15.  * General permission to copy or modify, but not for profit,  is
  16.  * hereby  granted,  provided that the above copyright notice is
  17.  * included and reference made to  the    fact  that  reproduction
  18.  * privileges were granted by DECUS.
  19.  *
  20.  */
  21.  
  22. /*
  23.  *            A R C H I V E
  24.  *
  25.  * Archiver, roughly from software tools.
  26.  *
  27.  */
  28.  
  29. /*
  30.  * title    ar    text file archiver
  31.  * index        text file archiver
  32.  * 
  33.  * synopsis
  34.  *     ar [-options] [-z logfile] archive_name file[s]
  35.  * 
  36.  * description
  37.  * 
  38.  *     Ar manages archives (libraries) of source files, allowing
  39.  *     a large number of small files to be stored without using
  40.  *     excessive system resources.  The following options may
  41.  *     be specified:
  42.  *
  43.  *         c    Force creation of new archive
  44.  *         d    Delete file from archive.
  45.  *         i    Insert, same as update
  46.  *         p    Print files on standard output
  47.  *         r    Replace, same as update
  48.  *         l    List archive contents (directory)
  49.  *         u    Update, same as replace
  50.  *         x    Extract named files
  51.  *         v    Verbose
  52.  *         z    Write verbose log to indicated file
  53.  *
  54.  *     The file name arguments may contain '*' and '?' wildcards, where
  55.  *     '*' matches any string of characters, and '?' matches one character.
  56.  *     ('%' may be used as a synonym for '?'.)  There is a slight, but
  57.  *     suble difference in the way wild cards are processed for the
  58.  *     various commands:
  59.  *
  60.  *         directory, delete, and extract
  61.  *
  62.  *     Match ('*' and '?') against the files in the
  63.  *     archive, performing the operation on all files that
  64.  *     match.  Except for delete, "no argument" matches
  65.  *     all files in the archive.
  66.  *
  67.  *         insert, replace, and update
  68.  *
  69.  *     Expand the wild-card arguments against the files
  70.  *     stored on the operating system -- eliminating all
  71.  *     wild cards.  Then, match against the archived
  72.  *     files.  Those that match are replaced by the
  73.  *     file on the operating system.  After replacement,
  74.  *     any additional files are appended to the archive.
  75.  *     Files in the archive that are not in the directory
  76.  *     are unchanged.
  77.  *
  78.  *     Currently, insert, replace, and update work the same.
  79.  *     If it seems reasonable, the program may be extended
  80.  *     as follows:
  81.  *
  82.  *         insert    Add files new only
  83.  *
  84.  *     Adds new files (not present in the archive)
  85.  *     but does not modify files currently in
  86.  *     the archive.  It would be an error to try
  87.  *     modifying a currently archived file.
  88.  *
  89.  *         replace    Modify existing files only
  90.  *
  91.  *     Modify files present in the archive, but do
  92.  *     not add new files to the archive.
  93.  *
  94.  *         update    Modify existing, add new
  95.  *
  96.  *     This is simple to do, but would seem to be a rich
  97.  *     source of user error.
  98.  * 
  99.  * archive file format
  100.  * 
  101.  *     Archive files are standard text files.  Each archive element is
  102.  *     preceeded by a line of the format:
  103.  * 
  104.  *         -h-    file.name    date    true_name
  105.  *
  106.  *     Note that there is no line or byte count.  To prevent problems,
  107.  *     a '-' at the beginning of a record within a user file or embedded
  108.  *     archive will be "quoted" by doubling it.  The date and true filename
  109.  *     fields are ignored.  On Dec operating systems, file.name is
  110.  *     forced to lowercase.
  111.  * 
  112.  * diagnostics
  113.  * 
  114.  *     Diagnostic messages should be self-explanatory
  115.  * 
  116.  * author
  117.  * 
  118.  *     Martin Minow
  119.  *
  120.  *    Extensively reworked by Fred Fish, 1-Dec-85
  121.  *      - Reformatted using the "indent" program.
  122.  *      - Added support for macro based debugging package.
  123.  *      - Delinted and questionable C usages removed.
  124.  *      - Ported to Commodore AMIGA using Lattice C.
  125.  * 
  126.  */
  127.  
  128. #include <stdio.h>
  129.  
  130. #ifdef BSD
  131. #include <ctype.h>
  132. #undef tolower
  133. #define tolower(c)    (isupper(c)?((c)+('a'-'A')):(c))
  134. #include <strings.h>
  135. #define strrchr=rindex
  136. #endif
  137.  
  138. #ifndef EOS
  139. #  define EOS '\000'
  140. #endif
  141.  
  142. #ifndef FALSE
  143. #  define FALSE (0)
  144. #endif
  145.  
  146. #ifndef TRUE
  147. #  define TRUE (1)
  148. #endif
  149.  
  150. /*
  151.  *    When calling Error(), the following flag bits define
  152.  *    optional processing.
  153.  */
  154.  
  155. #define WARN     (000001)        /* This is a warning message */
  156. #define ERR      (000002)        /* This is an error */
  157. #define FATAL    (000004)        /* Fatal, die after message */
  158. #define SYS    (000010)        /* System error number available */
  159.  
  160. /*
  161.  * The two routines fwild() and fnext() are faked on unix.
  162.  * Also faked on AMIGA for now, may do equivalent later.
  163.  */
  164.  
  165. #if unix || AMIGA
  166. #  define FAKEFWILD
  167. #endif
  168.  
  169. #ifdef FAKEFWILD
  170.   static FILE *fwild ();
  171.   static FILE *fnext ();
  172.   static void fgetname ();
  173. #else
  174.   extern  FILE *fwild ();        /* Wild card file lookup     */
  175.   extern  FILE *fnext ();        /* Open next wild card file     */
  176.   extern void fgetname ();
  177. #endif
  178.  
  179. #define    TEMPNAME "ar.tmp"
  180.  
  181. /*
  182.  * arfile chains together strings of text.
  183.  */
  184.  
  185. typedef struct arfile {
  186.     struct arfile *l_next;    /* -> next list element         */
  187.     int l_flag;            /* mark if found in archive     */
  188.     char *l_arname;        /* archive name argument     */
  189.     char *l_filename;        /* directory file name         */
  190. } ARFILE;
  191.  
  192. /*
  193.  * Global storage
  194.  */
  195.  
  196. FILE *arfd = NULL;        /* Archive             */
  197. FILE *newfd = NULL;        /* New archive             */
  198. FILE *logfd;            /* Log output             */
  199. char *logname;            /* Name of log file          */
  200. int newarchive = FALSE;        /* True if create from scratch     */
  201. int logging = FALSE;        /* True if log file enabled     */
  202. char text[513];            /* Working text             */
  203. char arname[81];        /* Current archive member name     */
  204. char filename[81];        /* Working file name         */
  205. char arfilename[81];        /* Archive file name         */
  206. char *timetext;            /* Time of day text         */
  207. int verbose = FALSE;        /* TRUE for verbosity         */
  208. int delflag = 0;        /* Delete files             */
  209. int directory = 0;        /* Table of contents if lit     */
  210. int update = 0;            /* Update files if lit         */
  211. int extract = 0;        /* Get files from archive     */
  212. int print = 0;            /* Write files to stdout     */
  213. int errorflag = 0;        /* Set on fatal error         */
  214. ARFILE *list = NULL;        /* String list header         */
  215.  
  216. #ifdef unix
  217. #  define delete unlink
  218. #endif
  219.  
  220. /*
  221.  *    The following allow use on systems that don't have my macro based
  222.  *    debugging package.  The default, for now, is to assume it is not
  223.  *    available.   Fred Fish, 1-Dec-85
  224.  */
  225.  
  226. #ifdef DBUG
  227. #  include <local/dbug.h>
  228. #else    /* !DBUG */
  229. #  define DBUG_ENTER(a)
  230. #  define DBUG_RETURN(a) return(a)
  231. #  define DBUG_VOID_RETURN return
  232. #  define DBUG_2(a,b)
  233. #  define DBUG_3(a,b,c)
  234. #  define DBUG_4(a,b,c,d)
  235. #  define DBUG_5(a,b,c,d,e)
  236. #  define DBUG_PUSH(a)
  237. #endif    /* DBUG */
  238.  
  239. /*
  240.  *    Declare internal functions that are used before definition seen.
  241.  */
  242.  
  243. static char *GetTime ();    /* Get current time as printable ascii */
  244. static void Error ();        /* Process an error or warning */
  245. static void doupdate ();
  246. static void dodirectory ();
  247. static void dodelete ();
  248. static void doextract ();
  249. static int replace ();
  250. static int expandargs ();
  251. static int findfiles ();
  252. static int savestring ();
  253. static void dumplist ();
  254. static void usage ();
  255. static void notfound ();
  256. static int gethdr ();
  257. static int findarg ();
  258. static int compare ();
  259. static int addfile ();
  260. static void argetname ();
  261. static void filemove ();
  262. static void arcopy ();
  263. static void arimport ();
  264. static void arexport ();
  265. static int match ();
  266. static int breakout ();
  267. static int match1 ();
  268.  
  269. /*
  270.  *    Library functions.
  271.  */
  272.  
  273. extern char *malloc ();
  274. extern char *strcpy ();
  275. extern char *strchr ();
  276. extern int delete ();
  277. extern void free ();
  278. extern void exit ();
  279.  
  280. #ifndef fflush            /* Sometimes is a macro */
  281.   extern void fflush ();
  282. #endif
  283.  
  284. /*
  285.  *    Main entry point.  Note declaration is 'int' and a meaningful
  286.  *    value is actually returned.  Generally this becomes the exit
  287.  *    status for the parent process.
  288.  */
  289.  
  290. int main (argc, argv)
  291. int argc;            /* Arg count             */
  292. char *argv[];            /* Arg vector             */
  293. {
  294.     register int i;        /* Random counter         */
  295.     register char *argp;    /* Arg pointer             */
  296.  
  297.     DBUG_ENTER ("main");
  298.     logfd = stderr;
  299.     logname = "stderr";
  300.     timetext = GetTime ();
  301.     for (i = 1; i < argc; i++) {
  302.     if ((argp = argv[i]) == NULL) {
  303.         continue;            /* From log file writer         */
  304.     }
  305.     if (*argp == '-') {
  306.         /* 
  307.          * Process options
  308.          */
  309.         argv[i] = NULL;        /* Erase it from file stuff     */
  310.         while (*++argp != EOS) {
  311.         switch (tolower (*argp)) {
  312.             case '#':        /* Can not bundle with other args! */
  313.                 DBUG_PUSH (argp);
  314.             argp = " ";    /* Trickery to terminate arg */
  315.             break;
  316.             case 'c': 
  317.             newarchive = TRUE;
  318.             break;
  319.             case 'd':     /* Delete from archive     */
  320.             delflag = 1;
  321.             break;
  322.             case 'p':     /* Print on stdout     */
  323.             print = 1;
  324.             break;
  325.             case 'l':     /* List directory     */
  326.             directory = 1;
  327.             break;
  328.             case 'h':    /* Explicit usage requested */
  329.                 usage ();
  330.             exit (0);
  331.             break;
  332.             case 'i':     /* Insert         */
  333.             case 'r':     /* Replace         */
  334.             case 'u':     /* Update modified     */
  335.             update = 1;
  336.             break;
  337.             case 'v':     /* Verbose         */
  338.             verbose = 1;
  339.             break;
  340.             case 'x':     /* Extract         */
  341.             extract = 1;
  342.             break;
  343.             case 'z':     /* Log file         */
  344.             logname = argv[i + 1];
  345.             if ((logfd = fopen (logname, "w")) == NULL) {
  346.                 Error (ERR|SYS|FATAL, "can't create logfile '%s'",
  347.                     logname);
  348.             }
  349.             if (verbose) {
  350.                 fprintf (stderr, "writing log to %s\n", logname);
  351.             }
  352.             logging = TRUE;
  353.             argv[i + 1] = NULL;
  354.             break;
  355.             default: 
  356.             Error (WARN|FATAL,
  357.                 "illegal option '%c', use -h for help",
  358.                 *argp);
  359.         }
  360.         }
  361.         argv[i] = NULL;    /* Erase argument     */
  362.     } else if (arfd == NULL && newfd == NULL) {    /* Not option */
  363.         /* 
  364.          * First file is the archive name
  365.          */
  366.         if (newarchive || (arfd = fopen (argp, "r")) == NULL) {
  367.         DBUG_3 ("new", "opening '%s' as new archive", argp);
  368.         if ((newfd = fopen (argp, "w")) == NULL) {
  369.             Error (ERR|FATAL|SYS, "can't create archive '%s'", argp);
  370.         } else {
  371.             newarchive = TRUE;
  372.             if (verbose) {
  373.             fprintf (logfd, "Creating new archive '%s'\n", argp);
  374.             }
  375.         }
  376.         }
  377.         argv[i] = NULL;    /* Erase argument     */
  378.         (void) strcpy (arfilename, argp);
  379.     }
  380.     }
  381.     if (errorflag) {
  382.     Error (ERR|FATAL, "previous error prevents continuation");
  383.     }
  384.     if (!newarchive && arfd == NULL) {
  385.     Error (ERR|FATAL, "no archive file specified, use -h for help");
  386.     }
  387.     /* 
  388.      * Got all arguments.
  389.      */
  390.     if ((i = delflag + directory + extract + print + update) > 1) {
  391.     Error (ERR|FATAL, "illogical option combination");
  392.     } else if (i == 0) {
  393.     if (verbose) {
  394.         fprintf (logfd, "Update selected by default\n");
  395.     }
  396.     update = 1;
  397.     }
  398.     if (!newarchive && (delflag || update)) {
  399.     if ((newfd = fopen (TEMPNAME, "w")) == NULL) {
  400.         Error (ERR|FATAL|SYS, "can't create work file '%s'", TEMPNAME);
  401.     }
  402.     }
  403.     /* 
  404.      * Debugging verbosity.
  405.      */
  406.     if (verbose) {
  407.     fprintf (logfd, "You have selected:");
  408.     if (directory) fprintf (logfd, " directory");
  409.     if (delflag) fprintf (logfd, " delete");
  410.     if (extract) fprintf (logfd, " extract");
  411.     if (print) fprintf (logfd, " print");
  412.     if (update) fprintf (logfd, " update");
  413.     if (verbose) fprintf (logfd, " and verbosity");
  414.     fprintf (logfd, ".\nArchive file is \"%s\".\n", arfilename);
  415.     }
  416.     if (expandargs (argc, argv, update)) {
  417.     Error (WARN, "errors found in arg expansion");
  418.     }
  419.     if (newarchive && !update) {
  420.     fprintf (logfd, "Dummy archive created\n");
  421.     (void) fclose (newfd);
  422.     } else if (directory) {
  423.     dodirectory ();
  424.     } else if (delflag) {
  425.     dodelete ();
  426.     } else if (extract || print) {
  427.     doextract (print);
  428.     } else if (update) {
  429.     doupdate ();
  430.     } else {
  431.     Error (FATAL|WARN, "no command was provided, use -h for help");
  432.     }
  433.     DBUG_RETURN (0);
  434. }
  435.  
  436. /*
  437.  * Write a table of contents
  438.  */
  439.  
  440. static void dodirectory ()
  441. {
  442.     DBUG_ENTER ("dodirectory");
  443.     text[0] = EOS;
  444.     while (gethdr (arfd)) {
  445.     if (findarg (arname, (char *) NULL)) {
  446.         printf (text);
  447.     }
  448.     arcopy (arfd, (FILE *) NULL);    /* Skip file         */
  449.     }
  450.     DBUG_VOID_RETURN;
  451. }
  452.  
  453.  
  454. /*
  455.  * Delete named files -- gotta have a name list
  456.  */
  457.  
  458. static void dodelete ()
  459. {
  460.     register int ecount;
  461.  
  462.     DBUG_ENTER ("dodelete");
  463.     if (list == NULL) {
  464.     Error (ERR|FATAL, "delete by name only");
  465.     }
  466.     ecount = replace (arfd, newfd, FALSE, 0);
  467.     notfound ();
  468.     (void) fclose (arfd);
  469.     (void) fclose (newfd);
  470.     if (ecount == 0) {
  471.     filemove (TEMPNAME, arfilename);
  472.     } else {
  473.     Error (WARN, "errors prevent deletion of archive");
  474.     if (logging) {
  475.         fprintf (logfd, "Errors prevent deletion of archive\n");
  476.     }
  477.     if (delete (TEMPNAME) == -1) {
  478.         Error (WARN|SYS, "can't delete '%s'", TEMPNAME);
  479.     }
  480.     }
  481.     DBUG_VOID_RETURN;
  482. }
  483.  
  484.  
  485. /*
  486.  * Extract or print named files
  487.  */
  488.  
  489. static void doextract (printflag)
  490. int printflag;            /* TRUE to print, FALSE to extract */
  491. {
  492.     register FILE *outfd;
  493.  
  494.     DBUG_ENTER ("doextract");
  495.     outfd = (printflag) ? stdout : NULL;
  496.     text[0] = EOS;
  497.     while (gethdr (arfd)) {
  498.     if (!findarg (arname, (char *) NULL)) {
  499.         if (verbose) {
  500.         fprintf (logfd, "Skipping \"%s\"\n", arname);
  501.         }
  502.         arcopy (arfd, (FILE *) NULL);        /* Skip         */
  503.     } else {
  504.         if (outfd != stdout) {
  505.         if ((outfd = fopen (arname, "w")) == NULL) {
  506.             Error (ERR|SYS, "can't create '%s'", arname);
  507.             if (logging) {
  508.             fprintf (logfd, "Can't create \"%s\"\n", arname);
  509.             }
  510.             arcopy (arfd, (FILE *) NULL);
  511.             continue;
  512.         }
  513.         }
  514.         if (verbose) {
  515.         fprintf (logfd, "Creating \"%s\"\n", arname);
  516.         }
  517.         arexport (arfd, outfd);
  518.         (void) fclose (outfd);
  519.         outfd = NULL;
  520.     }
  521.     }
  522.     DBUG_VOID_RETURN;
  523. }
  524.  
  525. /*
  526.  * Update existing files, add argv[1]..argv[argc-1] at end
  527.  */
  528.  
  529. static void doupdate ()
  530. {
  531.     register int ecount;
  532.     register ARFILE *lp;
  533.  
  534.     DBUG_ENTER ("doupdate");
  535.     ecount = 0;
  536.     if (!newarchive) {
  537.     DBUG_2 ("old", "update using existing archive");
  538.     ecount = replace (arfd, newfd, TRUE, 0);
  539.     }
  540.     for (lp = list; lp != NULL; lp = lp -> l_next) {
  541.     if (!lp -> l_flag) {
  542.         ecount += addfile (lp -> l_arname, lp -> l_filename, newfd,
  543.                     ecount, "Added");
  544.         lp -> l_flag = TRUE;
  545.     }
  546.     }
  547.     if (newarchive) {
  548.     DBUG_2 ("new", "new archive, no need to copy temp archive");
  549.     (void) fclose (newfd);
  550.     if (ecount) {
  551.         Error (WARN, "completed with %d errors", ecount);
  552.         if (logging) {
  553.         fprintf (stderr, "completed with %d errors\n", ecount);
  554.         }
  555.     }
  556.     } else {
  557.     DBUG_2 ("new", "copy temp archive to new archive");
  558.     (void) fclose (arfd);
  559.     (void) fclose (newfd);
  560.     if (ecount == 0) {
  561.         filemove (TEMPNAME, arfilename);
  562.     } else {
  563.         Error (WARN|SYS, "move of '%s' to '%s' supressed because of errors",
  564.             TEMPNAME, arfilename);
  565.         if (logging) {
  566.         fprintf (logfd,
  567.             "Move of %s to %s supressed because of errors\n",
  568.             TEMPNAME, arfilename);
  569.         }
  570.     }
  571.     }
  572.     DBUG_VOID_RETURN;
  573. }
  574.  
  575. /*
  576.  * Replace or delete files from the archive.  The updated archive
  577.  * is written to outfd.
  578.  */
  579.  
  580. static int replace (infd, outfd, updateflag, ecount)
  581. FILE *infd;        /* Reading files from here */
  582. FILE *outfd;        /* Writing files here */
  583. int updateflag;        /* TRUE to update, FALSE to remove */
  584. int ecount;
  585. {
  586.     DBUG_ENTER ("replace");
  587.     text[0] = EOS;        /* Signal gethdr initialization     */
  588.     while (gethdr (infd)) {
  589.         /* 
  590.          * We have a file, is it selected?
  591.          */
  592.     if (findarg (arname, filename)) {
  593.         if (updateflag) {
  594.         ecount += addfile (arname, filename, outfd, ecount,
  595.                    "Replaced");
  596.         }
  597.         arcopy (infd, (FILE *) NULL);
  598.     } else {
  599.         /* 
  600.          * Not selected for update, copy to the new archive
  601.          */
  602.         (void) fputs (text, outfd);
  603.         arcopy (infd, outfd);
  604.     }
  605.     }
  606.     DBUG_RETURN (ecount);
  607. }
  608.  
  609. /*
  610.  * Process the argv[] vector, building the argument list.
  611.  * Note: argv[1] is the first argument -- argv[0] is untouched and
  612.  * NULL entries in argv[] are ignored.
  613.  *
  614.  * If updateflag is TRUE, arguments are expanded against the file
  615.  * directory (using fwild/fnext).  If FALSE, they are used as is.
  616.  *
  617.  * Return TRUE if errors occurred.
  618.  */
  619.  
  620. static int expandargs (argc, argv, updateflag)
  621. int argc;        /* Number of arguments */
  622. char *argv[];        /* Arg vector */
  623. int updateflag;        /* TRUE to trigger file search */
  624. {
  625.     register int in;
  626.     register int eflag;
  627.  
  628.     DBUG_ENTER ("expandargs");
  629.     eflag = 0;
  630.     for (in = 1; in < argc; in++) {
  631.     if (argv[in] != NULL) {
  632.         if (updateflag) {
  633.         eflag += findfiles (argv[in]);
  634.         } else {
  635.         eflag += savestring (argv[in], (char *) NULL);
  636.         }
  637.     }
  638.     }
  639.     DBUG_RETURN (eflag != 0);
  640. }
  641.  
  642. /*
  643.  * Archive element names, do fwild lookup to expand wildcards where possible.
  644.  */
  645.  
  646. static int findfiles (fname)
  647. char *fname;
  648. {
  649.     register int i;
  650.     register FILE *fd;
  651.  
  652.     DBUG_ENTER ("findfiles");
  653.     if ((fd = fwild (fname, "r")) == NULL) {
  654.     Error (WARN|SYS, "can't open directory or file '%s'", fname);
  655.     if (logging) {
  656.         fprintf (stderr, "Can't open directory or wildcard file \"%s\"\n",
  657.             fname);
  658.     }
  659.     DBUG_RETURN (1);
  660.     }
  661.     /* 
  662.      * Locate each file, then save archive and file names
  663.      */
  664.     for (i = 0; fnext (fd) != NULL; i++) {
  665.     argetname (fd, arname, filename);
  666.     savestring (arname, filename);
  667.     }
  668.     if (i == 0) {
  669.     Error (WARN, "no match for '%s'", fname);
  670.     if (logging) {
  671.         fprintf (stderr, "Warning, no match for \"%s\"\n", fname);
  672.     }
  673.     DBUG_RETURN (1);
  674.     } else if (verbose) {
  675.     fprintf (logfd, "%d file%s in your directory match%s \"%s\"\n",
  676.         i,
  677.         (i > 1) ? "s" : "",
  678.         (i == 1) ? "es" : "",
  679.         fname);
  680.     DBUG_RETURN (0);
  681.     }
  682.     DBUG_RETURN (0);
  683. }
  684.  
  685. /*
  686.  * Insert text into the list in sorted order (on datum).
  687.  * Warn (and fail on) duplicates.
  688.  */
  689.  
  690. static int savestring (datum, file)
  691. char *datum;            /* Archive element name */
  692. char *file;            /* May be NULL if not necessary */
  693. {
  694.     register ARFILE *next;
  695.     register ARFILE **prev;
  696.     register ARFILE *new;
  697.     char *ardatum;
  698.     char *arfile;
  699.     int comp;
  700.  
  701.     DBUG_ENTER ("savestring");
  702.     arfile = NULL;
  703.     if (file != NULL) {
  704.     arfile = (char *) malloc ((unsigned) (strlen (file) + 1));
  705.     if (arfile == NULL) {
  706.         Error (ERR|FATAL|SYS, "can't allocate any more memory");
  707.     }
  708.     (void) strcpy (arfile, file);
  709.     }
  710.     if ((ardatum = (char *) malloc ((unsigned) (strlen (datum) + 1))) == NULL
  711.         || (new = (ARFILE *) malloc (sizeof (ARFILE))) == NULL) {
  712.         Error (ERR|FATAL|SYS, "can't allocate any more memory");
  713.     }
  714.     (void) strcpy (ardatum, datum);
  715.     new -> l_flag = FALSE;
  716.     new -> l_arname = ardatum;
  717.     new -> l_filename = arfile;
  718.     prev = &list;
  719.     next = list;
  720.     while (next != NULL && (comp = compare (datum, next -> l_arname)) > 0) {
  721.     if (comp == 0) {
  722.         Error (WARN, "duplicate argument '%s'", datum);
  723.         if (arfile) {
  724.         free (arfile);
  725.         }
  726.         free (ardatum);
  727.         free ((char *) new);
  728.         DBUG_RETURN (TRUE);
  729.     }
  730.     prev = &next -> l_next;
  731.     next = *prev;
  732.     }
  733.     *prev = new;
  734.     new -> l_next = next;
  735.     DBUG_RETURN (FALSE);
  736. }
  737.  
  738.  
  739. #ifdef DEADCODE        /* Not used, leftover from what?  (fnf) */
  740.  
  741. /*
  742.  * Dump archive name list -- used for debugging only
  743.  */
  744.  
  745. static void dumplist ()
  746. {
  747.     register ARFILE *lp;
  748.  
  749.     DBUG_ENTER ("dumplist");
  750.     if ((lp = list) == NULL) {
  751.     Error (WARN, "list is empty");
  752.     } else {
  753.     while (lp != NULL) {
  754.         fprintf (stderr, "%s, \"%s\"",
  755.                 (lp -> l_flag) ? "    found" : "not found",
  756.             lp -> l_arname);
  757.         if (lp -> l_filename == NULL) {
  758.         fprintf (stderr, "\n");
  759.         } else {
  760.         fprintf (stderr, "%s\n", lp -> l_filename);
  761.         }
  762.         lp = lp -> l_next;
  763.     }
  764.     }
  765.     DBUG_VOID_RETURN;
  766. }
  767.  
  768. #endif    /* DEADCODE */
  769.  
  770. static char *documentation[] = {
  771.     "Usage: portar -cdhilpruvx archive files",
  772.     "",
  773.     "  c  Create a new archive",
  774.     "  d  Delete named files from archive",
  775.     "  h  Print this help info",
  776.     "  i  Insert named files into archive",
  777.     "  l  List archive directory",
  778.     "  p  Print named files on standard output",
  779.     "  r  Replace named files",
  780.     "  u  Update -- replace named files",
  781.     "  v  Verbose -- give running commentary",
  782.     "  x  Extract -- copy named files to current directory",
  783.     "  z  Put logfile in file named in next argument",
  784.     "",
  785.     "i, r, and u, are identical",
  786.     "",
  787.     NULL
  788. };
  789.  
  790. static void usage ()
  791. {
  792.     register char **dp;
  793.     
  794.     DBUG_ENTER ("usage");
  795.     for (dp = documentation; *dp != NULL; dp++) {
  796.     printf ("%s\n", *dp);
  797.     }
  798.     DBUG_VOID_RETURN;
  799. }
  800.  
  801. static void Error (flags, fmt, arg1, arg2, arg3)
  802. int flags;
  803. char *fmt;
  804. char *arg1;
  805. char *arg2;
  806. char *arg3;
  807. {
  808.     fprintf (stderr, "portar: ");
  809.     if (flags & WARN) {
  810.     fprintf (stderr, "warning -- ");
  811.     } else if (flags & ERR) {
  812.     fprintf (stderr, "error -- ");
  813.     }
  814.     fprintf (stderr, fmt, arg1, arg2, arg3);
  815.     if (flags & SYS) {
  816.     perror ("");
  817.     } else {
  818.     fprintf (stderr, "\n");
  819.     }
  820.     (void) fflush (stderr);
  821.     if (flags & FATAL) {
  822.     exit (1);
  823.     }
  824. }
  825.  
  826. /*
  827.  * Called from dodelete() to warn the user about files that were
  828.  * to be deleted, but which were not in the archive.
  829.  */
  830.  
  831. static void notfound ()
  832. {
  833.     register ARFILE *lp;
  834.  
  835.     DBUG_ENTER ("notfound");
  836.     for (lp = list; lp != NULL; lp = lp -> l_next) {
  837.     if (!lp -> l_flag) {
  838.         Error (WARN|SYS, "can't delete '%s'", lp -> l_arname);
  839.         if (logging) {
  840.         fprintf (stderr, "Can't delete \"%s\" -- not found\n",
  841.             lp -> l_arname);
  842.         }
  843.     }
  844.     }
  845.     DBUG_VOID_RETURN;
  846. }
  847.  
  848. /*
  849.  * If text is null, read a record, returning TRUE if text contains a header.
  850.  * Parse the header into arname.
  851.  */
  852.  
  853. static int gethdr (fd)
  854. FILE *fd;
  855. {
  856.     register char *tp;
  857.     register char *np;
  858.  
  859.     DBUG_ENTER ("gethdr");
  860.     if (text[0] == EOS && fgets (text, (int) sizeof (text), fd) == NULL) {
  861.     DBUG_RETURN (FALSE);
  862.     }
  863.     if (text[0] != '-' || text[1] != 'h' || text[2] != '-') {
  864.     DBUG_RETURN (FALSE);
  865.     }
  866.     for (tp = &text[3]; *tp && *tp <= ' '; tp++);
  867.     for (np = &arname[0]; *tp > ' '; *np++ = *tp++);
  868.     *np = EOS;
  869.     DBUG_RETURN (TRUE);
  870. }
  871.  
  872. /*
  873.  * If name is in the list, mark it as "found" and return TRUE.
  874.  * If true, and fname is not NULL, fname will have the file argument.
  875.  */
  876.  
  877. static int findarg (name, fname)
  878. char *name;
  879. char *fname;            /* Gets full file name         */
  880. {
  881.     register ARFILE *lp;
  882.  
  883.     DBUG_ENTER ("findarg");
  884.     if ((lp = list) == NULL) {
  885.     if (fname != NULL) {
  886.         fname[0] = '\000';
  887.     }
  888.     DBUG_RETURN (TRUE);
  889.     }
  890.     while (lp != NULL) {
  891.     if (match (name, lp -> l_arname)) {
  892.         lp -> l_flag = TRUE;
  893.         if (fname != NULL) {
  894.         if (lp -> l_filename == NULL) {
  895.             fname[0] = EOS;
  896.         } else {
  897.             (void) strcpy (fname, lp -> l_filename);
  898.         }
  899.         }
  900.         DBUG_RETURN (TRUE);
  901.     }
  902.     lp = lp -> l_next;
  903.     }
  904.     DBUG_RETURN (FALSE);
  905. }
  906.  
  907. /*
  908.  * Compare strings (note: case insensitive)
  909.  */
  910.  
  911. static int compare (string1, string2)
  912. register char *string1;
  913. register char *string2;
  914. {
  915.     DBUG_ENTER ("compare");
  916.     while (tolower (*string1) == tolower (*string2)) {
  917.     if (*string1 == NULL) {
  918.         DBUG_RETURN (0);
  919.     }
  920.     string1++;
  921.     string2++;
  922.     }
  923.     DBUG_RETURN ((tolower (*string1) > tolower (*string2)) ? 1 : -1);
  924. }
  925.  
  926. /*
  927.  * Add file "fname" (archive element "name") to the archive
  928.  */
  929.  
  930. static int addfile (name, fname, outfd, ecount, why)
  931. char *name;            /* Archive element name */
  932. char *fname;            /* Archive file name */
  933. FILE *outfd;            /* Output file, already open */
  934. int ecount;            /* Current error count (updated */
  935. char *why;            /* Why are we here -- for verbosity */
  936. {
  937.     register FILE *infd;
  938.  
  939.     DBUG_ENTER ("addfile");
  940.     if ((infd = fopen (fname, "r")) == NULL) {
  941.     Error (WARN|SYS, "'%s' archive member '%s' not found", why,
  942.         (fname == NULL) ? "{Null}" : fname);
  943.     if (logging) {
  944.         fprintf (stderr, "%s archive member \"%s\" not found\n", why,
  945.             (fname == NULL) ? "{Null}" : fname);
  946.     }
  947.     ecount++;
  948.     } else {
  949. #ifdef DECUS
  950.     fgetname (infd, filename);
  951. #else
  952.     (void) strcpy (filename, fname);
  953. #endif
  954.     if (verbose) {
  955.         fprintf (logfd, "%s archive member \"%s\" (%s)\n", why, name,
  956.                 filename);
  957.     }
  958.     fprintf (outfd, "-h- %s\t%s\t%s\n", name, timetext, filename);
  959.     arimport (infd, outfd);
  960.     (void) fclose (infd);
  961.     }
  962.     DBUG_RETURN (ecount);
  963. }
  964.  
  965. /*
  966.  * Get file name, stripping off device:[directory] and ;version.
  967.  * The archive name ("FILE.EXT" is written to outname, while the
  968.  * full file name is written to outfilename.  On a dec operating system,
  969.  * outname is forced to lowercase.
  970.  */
  971.  
  972. static void argetname (fd, outname, outfilename)
  973. FILE *fd;
  974. char *outname;            /* Archive name */
  975. char *outfilename;        /* Full file name */
  976. {
  977.     register char *tp;
  978. #ifndef unix
  979.     char bracket;
  980. #endif
  981.     extern char *strrchr ();
  982.  
  983.     DBUG_ENTER ("argetname");
  984.     fgetname (fd, outfilename);
  985.     (void) strcpy (outname, outfilename);
  986. #ifdef    unix
  987.     /* 
  988.      * outname is after all directory information
  989.      */
  990.     if ((tp = strrchr (outname, '/')) != NULL) {
  991.     (void) strcpy (outname, tp + 1);
  992.     }
  993. #else
  994.     if ((tp = strrchr (outname, ';')) != NULL) {
  995.     *tp = EOS;
  996.     }
  997.     while ((tp = strchr (outname, ':')) != NULL) {
  998.     (void) strcpy (outname, tp + 1);
  999.     }
  1000.     switch (outname[0]) {
  1001.     case '[': 
  1002.         bracket = ']';
  1003.         break;
  1004.     case '<': 
  1005.         bracket = '>';
  1006.         break;
  1007.     case '(': 
  1008.         bracket = ')';
  1009.         break;
  1010.     default: 
  1011.         bracket = EOS;
  1012.         break;
  1013.     }
  1014.     if (bracket != EOS) {
  1015.     if ((tp = strchr (outname, bracket)) == NULL) {
  1016.         Error (WARN, "? Illegal file name '%s'", outfilename);
  1017.     } else {
  1018.         (void) strcpy (outname, tp + 1);
  1019.     }
  1020.     }
  1021.     for (tp = outname; *tp != EOS; tp++) {
  1022.     *tp = tolower (*tp);
  1023.     }
  1024. #endif
  1025.     DBUG_VOID_RETURN;
  1026. }
  1027.  
  1028.  
  1029. /*
  1030.  * "Rename" inname to outname the hard way.
  1031.  */
  1032.  
  1033. static void filemove (inname, outname)
  1034. char *inname;
  1035. char *outname;
  1036. {
  1037.     register FILE *infd;
  1038.     register FILE *outfd;
  1039.     long int nrecords;
  1040.  
  1041.     DBUG_ENTER ("filemove");
  1042.     if (verbose) {
  1043.     fprintf (logfd, "Copying %s to %s\n", inname, outname);
  1044.     }
  1045.     if ((infd = fopen (inname, "r")) == NULL) {
  1046.     Error (ERR|FATAL|SYS, "can't open '%s' for input", inname);
  1047.     }
  1048.     if ((outfd = fopen (outname, "w")) == NULL) {
  1049.     Error (ERR|FATAL|SYS, "can't open '%s' for write", outname);
  1050.     }
  1051.     for (nrecords = 0; fgets (text, (int) sizeof (text), infd) != NULL; nrecords++) {
  1052.     (void) fputs (text, outfd);
  1053.     }
  1054. #ifdef DECUS
  1055.     fgetname (infd, text);
  1056. #else
  1057.     (void) strcpy (text, inname);
  1058. #endif
  1059.     (void) fclose (infd);
  1060.     (void) fclose (outfd);
  1061.     if (delete (text) == -1) {
  1062.     Error (WARN|SYS, "can't delete '%s'", text);
  1063.     }
  1064.     if (verbose) {
  1065.     fprintf (logfd, "Archive %s contains %ld records.\n", outname,
  1066.             nrecords);
  1067.     }
  1068.     DBUG_VOID_RETURN;
  1069. }
  1070.  
  1071. /*
  1072.  * Copy (or skip if outfd == NULL) to next header
  1073.  */
  1074.  
  1075. static void arcopy (infd, outfd)
  1076. register FILE *infd;
  1077. register FILE *outfd;
  1078. {
  1079.     DBUG_ENTER ("arcopy");
  1080.     while (fgets (text, (int) sizeof (text), infd) != NULL) {
  1081.     if (text[0] == '-' && text[1] != '-') {
  1082.         DBUG_VOID_RETURN;
  1083.     }
  1084.     if (outfd != NULL) {
  1085.         (void) fputs (text, outfd);
  1086.     }
  1087.     }
  1088.     text[0] = EOS;        /* EOF signal         */
  1089.     DBUG_VOID_RETURN;
  1090. }
  1091.  
  1092. /*
  1093.  * Import text, writing it in the secret ar format.
  1094.  */
  1095.  
  1096. static void arimport (infd, outfd)
  1097. register FILE *infd;
  1098. register FILE *outfd;
  1099. {
  1100.     DBUG_ENTER ("arimport");
  1101.     while (fgets (text, (int) sizeof (text), infd) != NULL) {
  1102.     if (text[0] == '-') {
  1103.         (void) putc ('-', outfd);            /* Quote     */
  1104.     }
  1105.     (void) fputs (text, outfd);
  1106.     }
  1107.     DBUG_VOID_RETURN;
  1108. }
  1109.  
  1110. /*
  1111.  * Read secret archive format, writing archived data to outfd.
  1112.  * Clean out extraneous <cr>,<lf>'s
  1113.  */
  1114.  
  1115. static void arexport (infd, outfd)
  1116. register FILE *infd;
  1117. register FILE *outfd;
  1118. {
  1119.     register char  *tp;
  1120.  
  1121.     DBUG_ENTER ("arexport");
  1122.     while (fgets (text, (int) sizeof (text), infd) != NULL) {
  1123.     tp = &text[strlen (text)];
  1124.     if (tp > &text[1] && *--tp == '\n' && *--tp == '\r') {
  1125.         *tp++ = '\n';
  1126.         *tp = EOS;
  1127.     }
  1128.     if (text[0] == '-') {
  1129.         if (text[1] != '-') {
  1130.         DBUG_VOID_RETURN;
  1131.         }
  1132.         (void) fputs (text + 1, outfd);
  1133.     } else {
  1134.         (void) fputs (text, outfd);
  1135.     }
  1136.     }
  1137.     text[0] = EOS;
  1138.     DBUG_VOID_RETURN;
  1139. }
  1140.  
  1141. /*
  1142.  * Pattern match between
  1143.  *    name    string argument (FILE.EXT format)
  1144.  *    pattern    which may contain wildcards.
  1145.  *
  1146.  * Note: '*' matches all but '.' separator between file and ext.
  1147.  *    '?' matches one character, but not '.'
  1148.  *
  1149.  */
  1150.  
  1151. typedef struct filename {
  1152.     char namepart[10];
  1153.     char typepart[4];
  1154. } FILENAME;
  1155.  
  1156. static int match (name, pattern)
  1157. register char *name;
  1158. register char *pattern;
  1159. {
  1160.     FILENAME namebuff;
  1161.     FILENAME patternbuff;
  1162.     int result;
  1163.  
  1164.     DBUG_ENTER ("match");
  1165.     if (breakout (name, &namebuff) || breakout (pattern, &patternbuff)) {
  1166.     result = FALSE;
  1167.     } else {
  1168.     result = (match1 (namebuff.namepart, patternbuff.namepart)
  1169.         && match1 (namebuff.typepart, patternbuff.typepart));
  1170.     }
  1171.     DBUG_RETURN (result);
  1172. }
  1173.  
  1174. /*
  1175.  * Parse arg ("foo.bar") into "foo" and "bar"
  1176.  * Return TRUE if trouble.
  1177.  */
  1178.  
  1179. static int breakout (arg, buff)
  1180. char *arg;
  1181. FILENAME * buff;
  1182. {
  1183.     register char *ap;
  1184.     register char *bp;
  1185.     register int dotseen;
  1186.     int size;
  1187.  
  1188.     DBUG_ENTER ("breakout");
  1189.     dotseen = FALSE;
  1190.     ap = arg;
  1191.     bp = buff -> namepart;
  1192.     buff -> typepart[0] = EOS;
  1193.     size = (sizeof buff -> namepart) - 1;
  1194.     while (*ap != EOS) {
  1195.     if (*ap == '.') {
  1196.         if (dotseen++) {            /* 2 dots     */
  1197.         DBUG_RETURN (TRUE);
  1198.         } else {
  1199.         ap++;
  1200.         *bp = EOS;
  1201.         bp = buff -> typepart;
  1202.         size = (sizeof buff -> typepart) - 1;
  1203.         continue;
  1204.         }
  1205.     }
  1206.     if (size-- <= 0) {            /* 2 big     */
  1207.         DBUG_RETURN (TRUE);
  1208.     }
  1209.     *bp++ = *ap++;
  1210.     }
  1211.     *bp = EOS;
  1212.     DBUG_RETURN (FALSE);
  1213. }
  1214.  
  1215. /*
  1216.  * Recursive routine to match "name" against "pattern".
  1217.  * Returns TRUE if successful.
  1218.  */
  1219.  
  1220. static int match1 (name, pattern)
  1221. register char *name;        /* What to look for */
  1222. register char *pattern;        /* May have wildcard */
  1223. {
  1224.     register char pattbyte;
  1225.     char namebyte;
  1226.  
  1227.     DBUG_ENTER ("match1");
  1228.     for (;;) {
  1229.         /* 
  1230.          * First check for pattern ending in '*' -- this has to succeed
  1231.          */
  1232.     if ((pattbyte = *pattern++) == '*' && *pattern == EOS) {
  1233.         DBUG_RETURN (TRUE);
  1234.     }
  1235.         /* 
  1236.          * If not, then if both strings finish equally, it succeeds.
  1237.          */
  1238.     if ((namebyte = *name) == EOS && pattbyte == EOS) {
  1239.         DBUG_RETURN (TRUE);
  1240.     }
  1241.         /* 
  1242.          * Not at end of the name string.
  1243.          */
  1244.     switch (pattbyte) {
  1245.         case EOS:         /* End of pattern -> failure     */
  1246.         DBUG_RETURN (FALSE);
  1247.         case '*':         /* Wild card means "advance"     */
  1248.         do {
  1249.             if (match1 (name, pattern)) {
  1250.             DBUG_RETURN (TRUE);
  1251.             }
  1252.         } while (*name++ != EOS);
  1253.         DBUG_RETURN (FALSE);    /* Did our best             */
  1254.         default: 
  1255.         if (tolower (namebyte) != tolower (pattbyte)) {
  1256.             DBUG_RETURN (FALSE);
  1257.         }
  1258.         case '?':         /* One byte joker         */
  1259.         case '%':         /* RT11 one byte joker         */
  1260.         name++;        /* Matched this one         */
  1261.     }
  1262.     }
  1263. }
  1264.  
  1265. #ifdef    FAKEFWILD
  1266.  
  1267. /* Set if a file is open     */
  1268. /*  0    nothing open         */
  1269. /* +1    open, fnext not called     */
  1270. /* +2    fnext called once     */
  1271. static int  fake_flag = 0;
  1272.  
  1273. static char fake_name[81];    /* Name of file */
  1274.  
  1275. /*
  1276.  * "setup" to open a wildcard file name
  1277.  */
  1278.  
  1279. static FILE *fwild (fname, mode)
  1280. char *fname;
  1281. char *mode;
  1282. {
  1283.     register FILE *fd;
  1284.  
  1285.     DBUG_ENTER ("fwild");
  1286.     if (fake_flag != 0) {
  1287.     Error (WARN, "fwild/fnext out of sync");
  1288.     }
  1289.     fake_flag = 0;
  1290.     if ((fd = fopen (fname, mode)) != NULL) {
  1291.     fake_flag++;
  1292.     (void) strcpy (fake_name, fname);
  1293.     }
  1294.     DBUG_RETURN (fd);
  1295. }
  1296.  
  1297. static FILE *fnext (fd)
  1298. FILE *fd;
  1299. {
  1300.     DBUG_ENTER ("fnext");
  1301.     switch (fake_flag) {
  1302.     case 1: 
  1303.         fake_flag++;    /* First call after fwild     */
  1304.         DBUG_RETURN (fd);    /* File is "open"         */
  1305.     case 2: 
  1306.         fake_flag = 0;    /* Second call of fnext         */
  1307.         (void) fclose (fd);    /* Close existing file         */
  1308.         fake_name[0] = EOS;    /* Zap file name         */
  1309.         DBUG_RETURN ((FILE *)NULL);    /* No more files left         */
  1310.     default: 
  1311.         Error (WARN, "fnext called without calling fwild");
  1312.         DBUG_RETURN ((FILE *) NULL);
  1313.     }
  1314. }
  1315.  
  1316. /*
  1317.  *    Note, this only works for files opened via fwild/fnext. (fnf)
  1318.  */
  1319.  
  1320. static void fgetname (fd, name)
  1321. FILE *fd;
  1322. char *name;
  1323. {
  1324.     if (fd != NULL) {
  1325.     (void) strcpy (name, fake_name);
  1326.     }
  1327. }
  1328.  
  1329. #endif    /* FAKEFWILD */
  1330.  
  1331. #ifndef unix
  1332. perror (sp)
  1333. char *sp;
  1334. {
  1335.     if (sp != NULL) {
  1336.     fprintf (stderr, "%s", sp);
  1337.     }
  1338.     fprintf (stderr, ": <unknown error>");
  1339. }
  1340. #endif
  1341.  
  1342. #ifdef AMIGA
  1343.  
  1344. int delete (name)
  1345. char *name;
  1346. {
  1347.     int status;
  1348.     extern int DeleteFile ();
  1349.     
  1350.     DBUG_ENTER ("delete");
  1351.     if (DeleteFile (name)) {        /* Returns 0 or 1 */
  1352.     status = 0;            /* Success */
  1353.     } else {
  1354.     status = -1;            /* Failure */
  1355.     }
  1356.     DBUG_RETURN (status);
  1357. }
  1358.  
  1359. #endif    /* AMIGA */
  1360.  
  1361. /* 
  1362.  * Setup the time of day, erasing trailing '\n'
  1363.  */
  1364.  
  1365. #ifdef AMIGA
  1366. #  undef TRUE    /* TRUE, FALSE, and NULL are all redefined in types.h, */
  1367. #  undef FALSE    /* which is ultimately pulled in.  They have the same */
  1368. #  undef NULL    /* numeric values but are not enclosed in parens. */
  1369. #  include <libraries/dosextens.h>
  1370. #  include <libraries/dos.h>
  1371. #endif
  1372.  
  1373. static char *GetTime ()
  1374. {
  1375. #ifdef unix
  1376.     register char *cp;
  1377.     register char *now;
  1378.     long timval;
  1379.     extern long time ();
  1380.     extern char *ctime ();
  1381.  
  1382.     (void) time (&timval);
  1383.     now = ctime (&timval);
  1384.     cp = now + strlen (now);
  1385.     while (cp > now && *--cp <= ' ');
  1386.     cp[1] = EOS;
  1387.     return (now);
  1388. #else
  1389. #ifdef AMIGA
  1390.     auto struct DateStamp now;
  1391.     static char buffer[64];
  1392.     extern struct DateStamp *DateStamp ();
  1393.  
  1394.     if (DateStamp (&now) == NULL) {
  1395.     Error (WARN|SYS, "can't get current date");
  1396.     now.ds_Days = 0;
  1397.     now.ds_Minute = 0;
  1398.     now.ds_Tick = 0;
  1399.     }
  1400.     /* Kinda ugly for now, just dump structure as ascii. */
  1401.     sprintf (buffer, "%u:%u:%u", now.ds_Days, now.ds_Minute, now.ds_Tick);
  1402.     return (buffer);
  1403. #else
  1404.     return ("<time currently unavailable>");
  1405. #endif    /* AMIGA */
  1406. #endif    /* unix */
  1407. }
  1408.